//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this sample source code is subject to the terms of the Microsoft
// license agreement under which you licensed this sample source code. If
// you did not accept the terms of the license agreement, you are not
// authorized to use this sample source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the LICENSE.RTF on your install media or the root of your tools installation.
// THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
//
// (C) Copyright 2006 Marvell International Ltd.
// All Rights Reserved
//
//------------------------------------------------------------------------------
//
//  File:  timer.c
//
//  Marvell Littleton board initialization code.
//

#include <windows.h>
#include <nkintr.h>
#include <monahans.h>
#include <oal.h>

//------------------------------------------------------------------------------
// Defines 

// Maximal period set as ~17 Minutes
// Choosen to avoid overflow when it is convert to counter ticks
#define MAXPERIODMSEC               0xFFFFFFFF>>12
#define OEM_TICK_COUNT_MARGIN       5

//------------------------------------------------------------------------------
// External Variables 

//------------------------------------------------------------------------------
// Global Variables 
 
//------------------------------------------------------------------------------
// Local Variables 

static P_XLLP_OST_T 	pOSTRegs;

OAL_TIMER_STATE   g_oalTimer;


//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
//
//  Function:  OALTimerGetCount
//
//  Get the OS timer  counter register
//
static  UINT32 OALTimerGetCount()
{
    return pOSTRegs->oscr4;
}

//------------------------------------------------------------------------------
//
//  Function:  OALTimerSetCount
//
//  Set the OS timer  Counter register
//
static  VOID OALTimerSetCount(UINT32 val)
{
    volatile UINT32 temp;
    pOSTRegs->oscr4 = val;
    temp = pOSTRegs->oscr4;
    temp++;
}

//------------------------------------------------------------------------------
//
//  Function:  OALTimerGetCompare
//
//  Get the OS timer  match Register
//
static  UINT32 OALTimerGetCompare()
{
    return pOSTRegs->osmr4;
}

//------------------------------------------------------------------------------
//
//  Function:  OALTimerSetCompare
//
//  Set the OS timer match register 
//
static  VOID OALTimerSetCompare(UINT32 compare)
{
    volatile UINT32 temp;    
    pOSTRegs->osmr4 = compare;
    temp = pOSTRegs->osmr4;
    temp++;    
}



//------------------------------------------------------------------------------
// Local Functions 

BOOL OALTimerInit ()
{
    OALMSG(1, (L"+OALTimerInit\r\n"));

    // Initialize timer state global variable.
    //
    g_oalTimer.maxPeriodMSec          = MAXPERIODMSEC;
    g_oalTimer.margin         = OEM_TICK_COUNT_MARGIN;
    g_oalTimer.curCounts= 0;
    g_oalTimer.idleCounts= 0;

    // Initialize kernel-exported values.
    CurMSec = 0;
    idleconv     = 1;
    curridlehigh = 0;
    curridlelow = 0;

    // We will use variable system tick
    pOEMUpdateRescheduleTime = OALTimerUpdateRescheduleTime;

    // Obtain pointers to OST and INTC registers.
    pOSTRegs  = (XLLP_OST_T *) OALPAtoVA(MONAHANS_BASE_REG_PA_OST, FALSE);

    // Current usage of Match registers:
    //  M0 - High performance counter and others
    //  M1 - N/A
    //  M2 - N/A
    //  M3 - IPM Profiler
    //  M4 - Scheduler

    // Configure OSCR0 as high performance counter - 3.25Mhz
    // Set OSMR0 to the maximum value. match to add high performnce counter high part ++
    pOSTRegs->oscr0 = 0x1;                                  // Reset the OSCR0
    pOSTRegs->osmr0 = 0xFFFFFFFF;                           // Set the match as OSCR0 overflow - every 17mins
    //pOSTRegs->oier |= XLLP_OIER_E0;                         // Enable OSCR0 match interrupt
    //XllpINTCEnableSource(XLLP_INTC_S_OST_M0,XLLP_TRUE);     // Enable OSMR0 interrupt

    // Initialize high resolution timer function pointers
    pQueryPerformanceFrequency = OALTimerQueryPerformanceFrequency;
    pQueryPerformanceCounter   = OALTimerQueryPerformanceCounter;
    g_oalTimer.performanceHighPart = 0;
    g_oalTimer.perfLowLastRead = 0;


    // Configure OSCR4 as 32.768khz as OS timer
    pOSTRegs->omcr4 = XLLP_OMCR_CRES_32K | XLLP_OMCR_P;
    pOSTRegs->oier |= XLLP_OIER_E4;                         // Enable OSCR4 match interrupt
    XllpINTCEnableSource(XLLP_INTC_S_OST,XLLP_TRUE);    // Enable OSMR4 interrupt


    // Init the gloabl variables
    g_oalTimer.base = 0x1;

    OALTimerSetCount(g_oalTimer.base);
    OALTimerUpdatePeriod(g_oalTimer.maxPeriodMSec);

    // Let OSCR4 begin to run
    OALTimerSetCount(g_oalTimer.base);

    OALMSG(1, (L"-OALTimerInit\r\n"));

    return(TRUE);
}


//------------------------------------------------------------------------------
//
//  Function: OALTimerIntrHandler
//
//  This function implement timer interrupt handler. It is called from common
//  ARM interrupt handler.
//
UINT32 OALTimerIntrHandler()
{
    UINT32 sysIntr = SYSINTR_NOP;
    UINT32 count, period, perfCount;
    INT32 delta;
    static BOOL init = TRUE;

    //OALMSG(1, (L"+OALTimerIntrHandler\r\n"));

    // clear the timer interrupt
     pOSTRegs->ossr = XLLP_OSSR_M4;
	
    // Caculate the timer passed
    count = OALTimerGetCount();

    period = count - g_oalTimer.base;

    g_oalTimer.curCounts += period;

    // Directly set the count back to base
    g_oalTimer.base = count;

    // Calculate actual CurMSec
    // Even curCounts*125 could overflow, it's 64-bit result.
    // CurMSec is only 32-bit wide, which wraps earlier.
    CurMSec = (UINT32)((g_oalTimer.curCounts * 125) >> 12);

//    NKDbgPrintfW (_T("OALTimerIntrHandler, CurMSec:%d, count:%d, base:%d. period:%d\r\n"), CurMSec, count, g_oalTimer.base, period);

    // Reschedule?
    delta = dwReschedTime - CurMSec;

    // delta > 0 only happens if timer interupt happen after kernel set dwReschedTimer,
    // and before setup match register (via OALTimerUpdateRescheduleTime) 
    if (delta <= 0) {
        sysIntr = SYSINTR_RESCHED;
        delta = g_oalTimer.maxPeriodMSec;
    } else if (((UINT32)delta) > g_oalTimer.maxPeriodMSec)
        delta = g_oalTimer.maxPeriodMSec;

    // Set new period
    OALTimerUpdatePeriod(delta);

	// Check performance counter wrap condition
	perfCount = pOSTRegs->oscr0;
	if (perfCount < g_oalTimer.perfLowLastRead)
	{
	    // wrap happened. Need increase high part count
	    g_oalTimer.performanceHighPart ++;
	}
	g_oalTimer.perfLowLastRead = perfCount;

//    OALMSG(1, (L"-OALTimerIntrHandler\r\n"));
    return sysIntr;
}

//------------------------------------------------------------------------------
//
//  Function:  OALTimerUpdateRescheduleTime
//
//  This function is called by kernel to set next reschedule time.
//
VOID OALTimerUpdateRescheduleTime( DWORD timeMSec )
{
    UINT32 count, baseMSec, periodMSec;
    UINT32 delta;

    BOOL fWasEnabled;
	
	
    // To reduce the complexity of this function
    // Disable interrupt in this function
    fWasEnabled = INTERRUPTS_ENABLE (FALSE);

    // Get current system timer counter
    baseMSec = CurMSec;

    count = OALTimerGetCount();
    
    // Caculate, how far beyond next interrupt
    delta = g_oalTimer.match - count;

//    NKDbgPrintfW (_T("OALTimerUpdateRescheduleTime, CurMSec:%d, timeMSec:%d, count:%d, delta: %d\r\n"),baseMSec, timeMSec, count, delta);
    
    
    // we are within the margin timer or interrrupt alrady happened
    // Leave for the timer interrupt to handle it
    if ((delta < g_oalTimer.margin) || (delta > 0x8FFFFFFF)) goto cleanUp;

    // Calculate the distance between the new time and the last timer interrupt
    periodMSec = timeMSec - baseMSec;

    // Trying to set reschedule time prior or equal to CurMSec - this could
    // happen if a thread is on its way to sleep while preempted before
    // getting into the Sleep Queue
    if ((INT32)periodMSec < 0) {
        periodMSec = 0;
        }
    else if (periodMSec > g_oalTimer.maxPeriodMSec) {
        periodMSec = g_oalTimer.maxPeriodMSec;
    }

    // Now we find new period, so update timer
    OALTimerUpdatePeriod(periodMSec);

cleanUp:
    INTERRUPTS_ENABLE (fWasEnabled);
    return;
}

//------------------------------------------------------------------------------
//
//  Function: SC_GetTickCount
//
UINT32 SC_GetTickCount( )
{
    UINT64 baseCounts;
    UINT32 count, offset;

    do
        {
        baseCounts = g_oalTimer.curCounts;
        count = OALTimerGetCount();
        offset = count - g_oalTimer.base;
        }
    while (baseCounts != g_oalTimer.curCounts);
    return (UINT32)(((baseCounts + offset) * 125) >> 12);
}


//------------------------------------------------------------------------------
//
//  Function:  OALTimerUpdatePeriod
//
VOID OALTimerUpdatePeriod( UINT32 periodMSec )
{
    UINT32 period,count,  match;
    UINT32 delta;


    // Calculate count difference (this is optimized periodMSec * 32.768)
    period = (periodMSec << 12)/125 + 1;

    // This is the new compare value
    match = g_oalTimer.base + period;


    count = OALTimerGetCount();

    delta = match - count;

    // The new timer is in the past or within the margin time, trigger the inerrupt in margin time
    if((delta > 0x8FFFFFFF)||(delta < g_oalTimer.margin))
        match = count + g_oalTimer.margin;

    g_oalTimer.match = match;

    // Set value to hardware
    OALTimerSetCompare(match);

//    NKDbgPrintfW (_T("OALTimerUpdatePeriod, match:%d, osmr4:%d\r\n"), match, pOSTRegs->osmr4);

}


//------------------------------------------------------------------------------
//
//  Function:  OALTimerEarlyWakeup
//  This function is used to early wakeup AP
//
BOOL OALTimerEarlyWakeup( UINT32 earlyCounts )
{
    return TRUE;
}


//------------------------------------------------------------------------------
//
//  Function:  OALTimerClearEarlyWakeupInterrupt
//  this function is used to clear the early wakeup inerrrupt
//

// TODO
VOID OALTimerClearEarlyWakeupInterrupt()
{
    return;  
}

//------------------------------------------------------------------------------
//
//  Function: OALQueryPerformanceFrequency
//
//  This function returns the frequency of the high-resolution 
//  performance counter.
//
BOOL OALTimerQueryPerformanceFrequency(LARGE_INTEGER *pFrequency)
{
    if (!pFrequency) return FALSE;

    pFrequency->HighPart = 0;
    pFrequency->LowPart = 1000 * 3250;
    return TRUE;
}
    

//------------------------------------------------------------------------------
//
//  Function: OALQueryPerformanceCounter
//
//  This function returns the current value of the high-resolution 
//  performance counter.
//  
BOOL OALTimerQueryPerformanceCounter(LARGE_INTEGER *pCounter)
{
    
    if (!pCounter) return FALSE;
    
    // Update the Low Part of performance counter,directly from OSCR0
    pCounter->LowPart = pOSTRegs->oscr0;

    // Check performance counter wrap condition
	if (pCounter->LowPart < g_oalTimer.perfLowLastRead)
	{
	    // wrap happened. Need increase high part count
	    g_oalTimer.performanceHighPart ++;
        }
	g_oalTimer.perfLowLastRead = pCounter->LowPart;

    // Update the High Part of performance counter
    pCounter->HighPart = g_oalTimer.performanceHighPart;

    return TRUE;
}

//------------------------------------------------------------------------------
// 
//  Function: OALTimerPerformanceCounterHighUpdate
// 
//  This function is called by OSCR0 interrrupt handler 
//  update the high-resolution performance counter high part
//  Disabled due to OST errata
VOID OALTimerPerformanceCounterHighUpdate()
{
	return;
}

//------------------------------------------------------------------------------
